package coreModel;


import hitExceptions.InvalidDataException;
import hitExceptions.InvalidOperationException;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import reports.directors.IVisitor;


/**
 * This abstract class is the foundation of our usage of the composite structure.
 * @author drewski
 *
 */
@SuppressWarnings("serial")
public class ProductContainer extends SerializableImplementer 
	implements Comparable<ProductContainer>, INode
{	
	private String name;
	private Collection <ProductGroup> groupList;
	//id to identify two productContainers with the same name. for hashcode
	private Barcode productContainerID;


	public ProductContainer()
	{
		productContainerID=new Barcode();
		groupList = new ArrayList<ProductGroup>();
	}

	public ProductContainer(String name) throws InvalidDataException
	{
		if(name=="") throw new InvalidDataException("Product container name should be non empty");

		this.name=name;
		productContainerID= new Barcode();
		groupList = new ArrayList<ProductGroup>();
	}
	/**
	 * @param i a unique instance of the forementioned Product.
	 * @throws InvalidOperationException 
	 */
	public void addItem (Item i) throws InvalidOperationException
	{
		// Ammon - I changed this method, because Item already knows what its
		// Product is and we don't need to pass in a parameter with the Product.
		if(i.getParentContainer()!=this)
		{
			throw new InvalidOperationException("The Items parent container and the container " +
			"that tried to add it were different");
		}
		HIT.getInstance().getItemManager().addItem(i,true);
	}
	/**
	 * @param p the Product to be added into the container
	 */
	public void addProduct(Product p) throws InvalidOperationException
	{
		HIT.getInstance().getProductManager().addProductToContainer(p, this);
	}

	/**
	 * @param pg the ProductGroup to be added into the container
	 * @throws Exception when Product already exists
	 */
	public void addProductGroup(ProductGroup pg) throws Exception
	{
		if(!canAddProductGroup(pg))
			throw new InvalidDataException("can't add two product Groups with the same name");
		HIT.getInstance().getItemManager().addContainer(pg);
		groupList.add(pg);
	}


	public boolean canAddProductGroup(ProductGroup pg)
	{
		if(groupList.contains(pg)) 
			return false; 
		else 
			return true;
	}

	/**
	 * Used to help enforce data integrity
	 * @param name of the ProductGroup to be added
	 * @return T/F
	 */
	public boolean canAddProductGroup(String name)
	{
		if(groupList.isEmpty())
			return true;
		Iterator<ProductGroup> itr = groupList.iterator();

		while (itr.hasNext())
		{
			ProductGroup pg = (ProductGroup)itr.next();
			if (pg.getName().equals(name))
				return false;
		}

		return true;
	}

	public boolean canRemoveContainer() {

		if(HIT.getInstance().getItemManager().getAllItemsOfContainer(this)==null ||
				HIT.getInstance().getItemManager().getAllItemsOfContainer(this).isEmpty())
			return true;
		else
			return false;
	}


	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj)
	{
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		ProductContainer other = (ProductContainer) obj;
		if (productContainerID == null)
		{
			if (other.productContainerID != null)
				return false;
		} else if (!productContainerID.equals(other.productContainerID))
			return false;
		return true;
	}

	/**
	 * Gets the child product groups of this container. (not recursive)
	 * @return a collection of the immediate children of the given node.
	 */
	public Collection<ProductGroup> getChildren()
	{
		return groupList;
	}	

	/**
	 * Function to remove all ProductGroup children this Container has
	 */
	public void removeAllChildren()
	{
		groupList.clear();
	}

	/**
	 * Recursive method to find items that have expired.
	 * @return a list of Items that have expired.
	 */
	public Collection<Item> getExpiredItems()
	{
		return HIT.getInstance().getItemManager().getExpiredItems();
	}

	/**
	 * Retrieve a unique item by barcode
	 * @param barcode associated with a unique item
	 * @return the Item referenced by the barcode
	 */
	public Item getItem(Barcode bc)
	{	
		return HIT.getInstance().getItemManager().getItem(bc);
	}


	/**
	 * This function returns the Items that are directly within the Container
	 * @return a List of all the Items contained within the ProductContainer. 
	 */
	public Collection<Item> getItems()
	{
		return HIT.getInstance().getItemManager().getAllItemsOfContainer(this);	
	}

	/**
	 * @param p the Product which is being queried 
	 * @return a list of Items that belong to that Product in the given Product Container
	 */
	public Collection<Item> getItems(Product p)
	{	
		return HIT.getInstance().getItemManager().getAllItemsOfProduct(p);
	}

	/**
	 * This function is recursive throughout the data structure.
	 * @return a List of all the Items contained within the ProductContainer. 
	 */
	public List<Item> getItemsRecurse()
	{
		return null;
	}

	/**
	 * @return the name of the ProductContainer
	 */
	public String getName()
	{
		return name;
	}

	/**
	 * This returns the Products that are directly within the Container
	 * @return a List of Products
	 */
	public Collection<Product> getProducts()
	{
		return HIT.getInstance().getProductManager().getProductsOfContainer(this);		
	}

	/**
	 * This recursively returns the Products that are in the Container and its children
	 * @return a List of Products
	 */
	public List<Product> getProductsRecurse()
	{
		return null;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode()
	{
		return productContainerID.hashCode();
	}

	/**
	 * This method is used to help insure data integrity
	 * @param p the Product to be checked
	 * @return T/F
	 */
	public boolean productExists(Product p)
	{
		return HIT.getInstance().getProductManager().containsProductInContainer(p, this);
	}

	/**
	 * A recursive method that traverses the data structure to delete the Item related to the 
	 * barcode
	 * @param barcode
	 */
	public void removeItem(Barcode bc)
	{
		HIT.getInstance().getItemManager().removeItem(bc, false, false);
	}

	/**
	 * Will remove the named ProductGroup given that it is empty.
	 * @param name the name of the ProductGroup to be removed
	 * @throws InvalidOperationException when ProductGroup is not empty
	 */
	public void removeProductGroup(ProductContainer pc) throws InvalidOperationException 
	{
		if(pc.canRemoveContainer())
		{
			groupList.remove((ProductGroup)pc);		
		}	
		else
			throw new InvalidOperationException("Cannot remove productContainer");
	}


	/**
	 * @param name the name of the ProductContainer
	 * @throws InvalidOperationException 
	 * @throws Exception when name already exists in ProductGroup
	 */
	public void setName(String name) throws InvalidOperationException 
	{

		if(!canAddProductGroup(name))
			throw new InvalidOperationException("A StorageUnit with that name already exists");
		this.name = name;
	}

	public Collection<ProductGroup> getProductGroups() 
	{
		return groupList;
	}

	@Override
	public int compareTo(ProductContainer arg0) 
	{
		return name.compareTo(arg0.getName());
	}

	public String getID()
	{
		return new String( productContainerID.getValue() );
	}
	
	@Override
	public void preOrderAccept(IVisitor visitor)
	{
		
	}
	
	@Override
	public void postOrderAccept(IVisitor visitor)
	{
		
	}

	//added for database access and controler
	public Barcode getContainerID()
	{
		return productContainerID;
	}
	
	public void setContainerID(Barcode id)
	{
		this.productContainerID=id;
	}

}
